/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/thread.h"

/**************************************************************************
* Preprocessing
**************************************************************************/

/**************************************************************************
* Types
**************************************************************************/

/**************************************************************************
* Variables
**************************************************************************/

/**************************************************************************
* Static Variables
**************************************************************************/

/**************************************************************************
* Function Prototypes
**************************************************************************/

/**************************************************************************
* Function Implementations
**************************************************************************/
#if defined(THREAD_PTHREAD)
#include <pthread.h>
#include <unistd.h>
typedef void *(*pthread_thread_func)(void*);
void thread_create(thread_func func, void* data, BOOL waitable, thread_type* thread)
{
  (*thread) = malloc(sizeof(pthread_t));
  memset((*thread), 0, sizeof(pthread_t));
  pthread_attr_t pthread_attr;
  pthread_attr_init(&pthread_attr);
  if (waitable) {
    pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_JOINABLE);
  } else {
    pthread_attr_setdetachstate(&pthread_attr, PTHREAD_CREATE_DETACHED);
  }
  pthread_create((pthread_t*)(*thread), &pthread_attr, (pthread_thread_func)func, data);
  pthread_attr_destroy(&pthread_attr);
}

void thread_create_mutex(thread_mutex_type* mutex)
{
  (*mutex) = malloc(sizeof(pthread_mutex_t));
  memset((*mutex), 0, sizeof(pthread_mutex_t));
  pthread_mutex_init((pthread_mutex_t*)(*mutex), NULL);
}

void thread_create_rwlock(thread_rwlock_type* rwlock)
{
  (*rwlock) = malloc(sizeof(pthread_rwlock_t));
  memset((*rwlock), 0, sizeof(pthread_rwlock_t));
  pthread_rwlock_init((pthread_rwlock_t*)(*rwlock), NULL);
}

void thread_destroy_mutex(thread_mutex_type* mutex)
{
  pthread_mutex_destroy((pthread_mutex_t*)(*mutex));
  free(*mutex);
}

void thread_destroy_rwlock(thread_rwlock_type* rwlock)
{
  pthread_rwlock_destroy((pthread_rwlock_t*)(*rwlock));
  free(*rwlock);
}

void thread_exit(void)
{
  pthread_exit(NULL);
}

void thread_kill(thread_type* thread)
{
  pthread_cancel(*((pthread_t*)(*thread)));
  free(*thread);
}

void thread_lock(thread_mutex_type* mutex)
{
  pthread_mutex_lock((pthread_mutex_t*)(*mutex));
}

void thread_lock_read(thread_rwlock_type* rwlock)
{
  pthread_rwlock_rdlock((pthread_rwlock_t*)(*rwlock));
}

void thread_lock_write(thread_rwlock_type* rwlock)
{
  pthread_rwlock_wrlock((pthread_rwlock_t*)(*rwlock));
}

void thread_sleep(int milliseconds)
{
  usleep(milliseconds * 1000);
}

void thread_unlock(thread_mutex_type* mutex)
{
  pthread_mutex_unlock((pthread_mutex_t*)(*mutex));
}

void thread_unlock_read(thread_rwlock_type* rwlock)
{
  pthread_rwlock_unlock((pthread_rwlock_t*)(*rwlock));
}

void thread_unlock_write(thread_rwlock_type* rwlock)
{
  pthread_rwlock_unlock((pthread_rwlock_t*)(*rwlock));
}

void thread_wait(thread_type* thread)
{
  pthread_join(*((pthread_t*)(*thread)), NULL);
}

#elif defined(THREAD_WIN)
typedef struct
{
  thread_mutex_type mutexes[2]; // 0-READ, 1-WRITE
} win_thread_rwlock_type;
void thread_create(thread_func func, void* data, BOOL waitable, thread_type* thread)
{
  (*thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)func, data, 0, NULL);
}

void thread_create_mutex(thread_mutex_type* mutex)
{
  (*mutex) = CreateMutex(NULL, FALSE, NULL);
}

void thread_create_rwlock(thread_rwlock_type* rwlock)
{
  (*rwlock) = malloc(sizeof(win_thread_rwlock_type));
  memset((*rwlock), 0, sizeof(win_thread_rwlock_type));
  thread_create_mutex(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[0]));
  thread_create_mutex(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[1]));
}

void thread_destroy_mutex(thread_mutex_type* mutex)
{
  free(*mutex);
  (*mutex) = NULL;
}

void thread_destroy_rwlock(thread_rwlock_type* rwlock)
{
  thread_destroy_mutex(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[0]));
  thread_destroy_mutex(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[1]));
  free(*rwlock);
  (*rwlock) = NULL;
}

void thread_exit(void)
{
  ExitThread(0);
}

void thread_kill(thread_type* thread)
{
  TerminateThread(*thread, 0);
}

void thread_lock(thread_mutex_type* mutex)
{
  WaitForSingleObject(*mutex, INFINITE);
}

void thread_lock_read(thread_rwlock_type* rwlock)
{
  thread_lock(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[0]));
}

void thread_lock_write(thread_rwlock_type* rwlock)
{
  WaitForMultipleObjects(2, ((win_thread_rwlock_type*)(*rwlock))->mutexes, TRUE, INFINITE);
}

void thread_sleep(int milliseconds)
{
  Sleep(milliseconds);
}

void thread_unlock(thread_mutex_type* mutex)
{
  ReleaseMutex(*mutex);
}

void thread_unlock_read(thread_rwlock_type* rwlock)
{
  thread_unlock(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[0]));
}

void thread_unlock_write(thread_rwlock_type* rwlock)
{
  thread_unlock(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[1]));
  thread_unlock(&(((win_thread_rwlock_type*)(*rwlock))->mutexes[0]));
}

void thread_wait(thread_type* thread)
{
  WaitForSingleObject(*thread, INFINITE);
}

#elif defined(THREAD_CUSTOM)


#else
// no threads
#include <unistd.h>
void thread_create(thread_func func, void* data, BOOL waitable, thread_type* thread) {}
void thread_create_mutex(thread_mutex_type* mutex) {}
void thread_create_rwlock(thread_rwlock_type* rwlock) {}
void thread_destroy_mutex(thread_mutex_type* mutex) {}
void thread_destroy_rwlock(thread_rwlock_type* rwlock) {}
void thread_exit(void) {}
void thread_kill(thread_type* thread) {}
void thread_lock(thread_mutex_type* mutex) {}
void thread_lock_read(thread_rwlock_type* rwlock) {}
void thread_lock_write(thread_rwlock_type* rwlock) {}
void thread_sleep(int milliseconds)
{
  usleep(milliseconds * 1000);
}
void thread_unlock(thread_mutex_type* mutex) {}
void thread_unlock_read(thread_rwlock_type* rwlock) {}
void thread_unlock_write(thread_rwlock_type* rwlock) {}
void thread_wait(thread_type* thread) {}

#endif
